/*  ----------------------------------------------------------------------------
 *  Arello Mobile
 *  Fast 3d Engine
 *  ----------------------------------------------------------------------------
 * Except where otherwise noted, this work is licensed under
 * http://creativecommons.org/licenses/by/3.0
 */

#include <lib3d/hard/Hardware.h>
#include <xml/Stream.h>
#include <Converter.h>
#include <safe_new.h>

#include "ModelMorph.h"

namespace liba
{
namespace lib3d
{
namespace node
{

const Atom ModelMorph::node_name("ModelMorph");
const Atom ModelMorph::morph_weight_name("MorphWeight");

void ModelMorph::HaMod::on_add_hardware(hard::Manager::hardware_type * ha)
{
	hard_vector.push_back( SweetPair(ha, new model::ModelInHardwareObject(ha->create_vertex_buffer( true ), 0)) );
}

ModelMorph::HaMod::HaMod()
{
	hard::Manager::add_dependent( this );
}

ModelMorph::HaMod::~HaMod()
{
	hard::Manager::remove_dependent( this );
}

ModelMorph::HaMod::HaMod(const HaMod & other)
{
	hard::Manager::add_dependent(this);
}

ModelMorph::ModelMorph( const Atom & name, Node * parent )
:	Model( name, parent ),
	difference_calculated( false ),
	calculated_morph_weight( -1 )
{
	this->RegisterProperty(morph_weight_name, &this->morphWeight);
	node_type = node_name;
}

ModelMorph::ModelMorph(const ModelMorph& rhs)
	: Model(rhs)
	, normal_dst(rhs.normal_dst)
	, normal_model_src1(rhs.normal_model_src1)
	, normal_model_src2(rhs.normal_model_src2)
	, dst_positions(rhs.dst_positions)
	, dst_normals(rhs.dst_normals)
	, dst_uv0s(rhs.dst_uv0s)
	, difference_calculated(rhs.difference_calculated)
	, calculated_morph_weight(rhs.calculated_morph_weight)
{
	this->RegisterProperty(morph_weight_name, &this->morphWeight);
}

ModelMorph::~ModelMorph()
{}

ModelMorph * ModelMorph::Clone()
{
	return new ModelMorph(*this);
}

bool ModelMorph::on_attribute( xml::Provider * prov, const std::string & name, const std::string & value )
{
	if ( name == "Source" )
	{
		normal_model_src1.load_rep( value.c_str() );
		return true;
	}
	if ( name == "Shadow" )
	{
//		shadow_model.load_rep( value );
		return true;
	}
	if ( name == "Source2" )
	{
		normal_model_src2.load_rep( value.c_str() );
		return true;
	}
	if ( name == "Shadow2" )
	{
//		shadow_model2.load_rep( value );
		return true;
	}
	if( name == morph_weight_name.get_str().c_str() )
	{
		double morphWeight;
		if (!converter::convert( value, &morphWeight ))
			return false;
		this->morphWeight = morphWeight;
		return true;
	}
	return Model::on_attribute( prov, name, value );
}

/*bool ModelMorph::render( hard::Hardware * hardware, const CameraDesc & camera_desc )
{
	bool result = get_rep()->render( get_shaders(), hardware, camera_desc ); // BUG result is unused
	return Model::render( hardware, camera_desc );
}*/

void ModelMorph::RenderToList( hard::Hardware * hardware, hard::RenderList * render_list )
{
	hard::RenderModel model_ref;
	model_ref.world = GetGlobalMatrix();

	if( !normal_model_src1.get_rep() || !normal_model_src1.get_rep()->is_loaded() ||
		!normal_model_src2.get_rep() || !normal_model_src2.get_rep()->is_loaded() )
		return;

	model::ModelInHardwareObject * object = normal_dst.find_data(hardware);

	if( calculated_morph_weight != morphWeight || !object->vb->is_valid() )
	{
		const SimpleArray<float> * positions1 = normal_model_src1.get_rep()->find_value_float( model::Model::position_name );
		const SimpleArray<float> * normals1 = normal_model_src1.get_rep()->find_value_float( model::Model::normal_name );
		const SimpleArray<float> * uv01 = normal_model_src1.get_rep()->find_value_float( model::Model::uv0_name );

		const SimpleArray<float> * positions2 = normal_model_src2.get_rep()->find_value_float( model::Model::position_name );
		const SimpleArray<float> * normals2 = normal_model_src2.get_rep()->find_value_float( model::Model::normal_name );
		const SimpleArray<float> * uv02 = normal_model_src2.get_rep()->find_value_float( model::Model::uv0_name );

		const SimpleArray<float> * out_positions = 0;
		const SimpleArray<float> * out_normals = 0;
		const SimpleArray<float> * out_uv0s = 0;

		if( positions1->size() != positions2->size() )
			THROW(Gluck(__FUNCTION__, "Morph: models not match" ));

		if( !difference_calculated )
		{
			if( memcmp( positions1->data(), positions2->data(), positions1->size() * sizeof( float ) ) )
			{
				dst_positions.resize( positions1->size() );
			}
			if( memcmp( normals1->data(), normals2->data(), normals1->size() * sizeof( float ) ) )
			{
				dst_normals.resize( normals1->size() );
			}
			if( memcmp( uv01->data(), uv02->data(), uv01->size() * sizeof( float ) ) )
			{
				dst_uv0s.resize( uv01->size() );
			}

			difference_calculated = true;
		}
		if( calculated_morph_weight != morphWeight || !object->vb->is_valid() )
		{
			calculated_morph_weight = morphWeight;
			double w = calculated_morph_weight;

			if( dst_positions.size() )
			{
				//unrolling loop as much as we can
				for( size_t i = 0; i < positions1->size(); )
				{
					dst_positions[i] = (*positions1)[i] + w * ( (*positions2)[i] - (*positions1)[i] );
					++i;
					dst_positions[i] = (*positions1)[i] + w * ( (*positions2)[i] - (*positions1)[i] );
					++i;
					dst_positions[i] = (*positions1)[i] + w * ( (*positions2)[i] - (*positions1)[i] );
					++i;
				}
				out_positions = &dst_positions;
			}
			else
				out_positions = positions1;

			if( dst_normals.size() )
			{
				//unrolling loop as much as we can
				for( size_t i = 0; i < normals1->size(); )
				{
					dst_normals[i] = (*normals1)[i] + w * ( (*normals2)[i] - (*normals1)[i] );
					++i;
					dst_normals[i] = (*normals1)[i] + w * ( (*normals2)[i] - (*normals1)[i] );
					++i;
					dst_normals[i] = (*normals1)[i] + w * ( (*normals2)[i] - (*normals1)[i] );
					++i;
				}
				out_normals = &dst_normals;
			}
			else
				out_normals = normals1;

			if( dst_uv0s.size() )
			{
				//unrolling loop as much as we can
				for( size_t i = 0; i < uv01->size(); )
				{
					dst_uv0s[i] = (*uv01)[i] + w * ( (*uv02)[i] - (*uv01)[i] );
					++i;
					dst_uv0s[i] = (*uv01)[i] + w * ( (*uv02)[i] - (*uv01)[i] );
					++i;
				}
				out_uv0s = &dst_uv0s;
			}
			else
				out_uv0s = uv01;

			SimpleArray<float> empty_float;
			SimpleArray<int> empty_int;

			object->vb->update( *out_positions, *out_normals, empty_float, empty_float,
				*out_uv0s, empty_float, empty_float, empty_float, empty_float,
				empty_int, empty_int, empty_int, empty_int );
		}
	}

	model_ref.vb = object->vb;

	material::Shaders::const_iterator sit = get_shaders().begin();
	int primitive_pos = 0;
	int vertex_pos = 0;
	for( ; sit != get_shaders().end(); ++sit )
	{
		int primitive_count = (*sit)->get_triangles_count();
		int vertex_count = (*sit)->get_vertex_count();

		hard::RenderModel render_model;
		render_model.world = model_ref.world;
		render_model.vb = model_ref.vb;
		render_model.primitive_start = primitive_pos;
		render_model.primitive_count = primitive_count;
		render_model.ib = object->ib;
		if( object->ib )
		{
			render_model.vertex_start = vertex_pos;
			render_model.vertex_count = vertex_count;
		}

		(*sit)->render_to_list( hardware, &render_model.material );

		render_list->models.push_back( render_model );
		primitive_pos += primitive_count*3;
		vertex_pos += vertex_count;
	}

/*
	model_ref.morph_weight = morph_weight;
	if( normal_model.get_rep() && normal_model2.get_rep() )
	{
		normal_model2.get_rep()->fill_vb2( hardware, &model_ref );
		normal_model.get_rep()->render_to_list( get_shaders(), hardware, render_list, model_ref );
	}
	if( shadow_model.get_rep() && shadow_model2.get_rep() )
	{
		shadow_model2.get_rep()->fill_vb2( hardware, &model_ref );
		shadow_model.get_rep()->render_to_shadow_list( get_shaders(), hardware, render_list, model_ref );
	}
*/
	return Model::RenderToList( hardware, render_list );
}

} // namespace node
} // namespace lib3d
} // namespace liba

